﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace Inaction.Linq
{
    public static class ExpressionVisitorExtensions
    {
        public static bool CanInferType(this Type type)
        {
            return !(type.Namespace == null
                && type.ReflectedType == null
                && type.BaseType == typeof(System.Object));
        }

        public static object GetValue(this MemberExpression member)
        {
            var objectMember = Expression.Convert(member, typeof(object));

            var getterLambda = Expression.Lambda<Func<object>>(objectMember);

            var getter = getterLambda.Compile();

            return getter();
        }
        public static string GetOperator(this ExpressionVisitor visitor, string methodName)
        {
            switch (methodName)
            {
                case "Add": return "+";
                case "Subtract": return "-";
                case "Multiply": return "*";
                case "Divide": return "/";
                case "Negate": return "-";
                case "Remainder": return "%";
                default: return null;
            }
        }
        public static string GetOperator(this ExpressionVisitor visitor, UnaryExpression u)
        {
            switch (u.NodeType)
            {
                case ExpressionType.Negate:
                case ExpressionType.NegateChecked:
                    return "-";
                case ExpressionType.UnaryPlus:
                    return "+";
                case ExpressionType.Not:
                    return IsBoolean(u.Operand.Type) ? "Not" : "~";
                default:
                    return "";
            }
        }
        public static string GetOperator(this ExpressionVisitor visitor, BinaryExpression b)
        {
            return GetOperator(visitor, b.NodeType);
        }
        public static string GetOperator(this ExpressionVisitor visitor, ExpressionType nodeType)
        {
            switch (nodeType)
            {
                case ExpressionType.And:
                    return "&";
                case ExpressionType.AndAlso:
                    return "And";
                case ExpressionType.Or:
                    return "|";
                case ExpressionType.OrElse:
                    return "Or";
                case ExpressionType.Equal:
                    return "=";
                case ExpressionType.NotEqual:
                    return "<>";
                case ExpressionType.LessThan:
                    return "<";
                case ExpressionType.LessThanOrEqual:
                    return "<=";
                case ExpressionType.GreaterThan:
                    return ">";
                case ExpressionType.GreaterThanOrEqual:
                    return ">=";
                case ExpressionType.Add:
                case ExpressionType.AddChecked:
                    return "+";
                case ExpressionType.Subtract:
                case ExpressionType.SubtractChecked:
                    return "-";
                case ExpressionType.Multiply:
                case ExpressionType.MultiplyChecked:
                    return "*";
                case ExpressionType.Divide:
                    return "/";
                case ExpressionType.Modulo:
                    return "%";
                case ExpressionType.ExclusiveOr:
                    return "^";
                default:
                    return "";
            }
        }

        public static string GetAggregateName(this ExpressionVisitor visitor, string methodName)
        {
            return GetAggregateName(visitor, GetAggregateDbExpressionType(visitor, methodName));
        }

        public static string GetAggregateName(this ExpressionVisitor visitor, DbExpressionType aggregateType)
        {
            switch (aggregateType)
            {
                case DbExpressionType.Count: return "COUNT";
                case DbExpressionType.Min: return "MIN";
                case DbExpressionType.Max: return "MAX";
                case DbExpressionType.Sum: return "SUM";
                case DbExpressionType.Average: return "AVG";
                default: throw new Exception(string.Format("Unknown aggregate type: {0}", aggregateType));
            }
        }
        public static DbExpressionType GetAggregateDbExpressionType(this ExpressionVisitor visitor, string methodName)
        {
            switch (methodName)
            {
                case "Count": return DbExpressionType.Count;
                case "Min": return DbExpressionType.Min;
                case "Max": return DbExpressionType.Max;
                case "Sum": return DbExpressionType.Sum;
                case "Average": return DbExpressionType.Average;
                default: throw new Exception(string.Format("Unknown aggregate type: {0}", methodName));
            }
        }

        public static bool IsBoolean(this Type type)
        {
            return type == typeof(bool) || type == typeof(bool?);
        }

        public static bool IsPredicate(this Expression expr)
        {
            switch (expr.NodeType)
            {
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                    return IsBoolean(((BinaryExpression)expr).Type);
                case ExpressionType.Not:
                    return IsBoolean(((UnaryExpression)expr).Type);
                case ExpressionType.Equal:
                case ExpressionType.NotEqual:
                case ExpressionType.LessThan:
                case ExpressionType.LessThanOrEqual:
                case ExpressionType.GreaterThan:
                case ExpressionType.GreaterThanOrEqual:

                    return true;
                case ExpressionType.Call:
                    return IsBoolean(((MethodCallExpression)expr).Type);
                case ExpressionType.MemberAccess:
                    return IsBoolean(((MemberExpression)expr).Type);
                default:
                    return false;
            }
        }

        public static bool IsLogicalOperator(this ExpressionType expressionType)
        {
            switch (expressionType)
            {
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                case ExpressionType.ExclusiveOr:
                case ExpressionType.Not:
                    return true;
                default:
                    return false;
            }
        }


        public static Expression UpdatePredicate(this ExpressionVisitor visitor, Expression lambda_body)
        {
            switch (lambda_body.NodeType)
            {
                case ExpressionType.MemberAccess:
                    return Expression.MakeBinary(ExpressionType.Equal, Expression.Constant(true), lambda_body);
                case ExpressionType.Call:
                    return (MethodCallExpression)lambda_body;
                default:
                    return (BinaryExpression)lambda_body;
            }
        }
        public static LambdaExpression GetLambda(this ExpressionVisitor visitor, Expression e)
        {
            while (e.NodeType == ExpressionType.Quote)
            {
                e = ((UnaryExpression)e).Operand;
            }
            if (e.NodeType == ExpressionType.Constant)
            {
                return ((ConstantExpression)e).Value as LambdaExpression;
            }
            return e as LambdaExpression;
        }
        public static Expression StripQuotes(this ExpressionVisitor visitor, Expression e)
        {
            while (e.NodeType == ExpressionType.Quote)
            {
                e = ((UnaryExpression)e).Operand;
            }
            return e;
        }

        public static bool IsScalar(this Type type)
        {
            type = type.UnderlyingSystemType;// TypeHelper.GetNonNullableType(type);
            switch (Type.GetTypeCode(type))
            {
                case TypeCode.Empty:
                case TypeCode.DBNull:
                    return false;
                case TypeCode.Object:
                    return
                        type == typeof(DateTime) ||
                        type == typeof(DateTimeOffset) ||
                        type == typeof(Decimal) ||
                        type == typeof(Guid) ||
                        type == typeof(byte[]);
                default:
                    return true;
            }
        }
    }

}
